Vlad Karpov homepage
Sorry for bad English | Простите за плохой Русский |
Range |
E>>>>>> Здравствуйте, Влад. E>>>>>> Маленький вопрос... E>>>>>> Как отобрать записи с пустым полем типа дата E>>>>>> (в DBU'шке это поле такое - ' / / ')? E>>>>>> Я пытаюсь делать setrange, но не могу понять как обозначить пустое E>>>>>> поле даты: NTX3.Indexes[5].SetRangeFields('DATE_G',[???]) ? E>>>>>> Да, кстати, как делать setrange по двум и более полям? E>>>>>> Это зависит от того, как эти поля входят в индекс? - преобразуются в E>>>>>> другой тип, порядок полей или как? K>>>>> Сколько бы полей не входило в выражение ключа индекса, после расчета K>>>>> этого ключа в NTX ВСЕГДА происходит преобразование значения ключа в K>>>>> строку. NULL значения (твоя дата) преобразуются в строку пробелов K>>>>> длинной, зависимой от типа данных. Для стандартной даты это строка из 8 K>>>>> пробелов. Именно эти текстовые строки и хранит файл NTX в качестве K>>>>> ключей. K>>>>> Теперь про Range. Range в конечном итоге накладывается на эти ключи, K>>>>> т.е. на строки. Все функции, которые устанавливают Range сводятся к K>>>>> NTXRange.HiKey := Key; K>>>>> NTXRange.LoKey := Key; K>>>>> NTXRange.ReOpen; K>>>>> где HiKey и LoKey - строки. Range выбирает из индекса ключи которые K>>>>> попадают в интервал путем обычного сравнения строк, причем длинна K>>>>> HiKey и LoKey может и отличаться от длинны ключа в индексе. K>>>>> Тебе надо либо вручную устанавливать строковые границы Range, либо K>>>>> использовать NTX3.Indexes[5].SetRangeFields в варианте с массивом K>>>>> вариантов, и там в качестве значений полей указывать NULL. K>>>>> Да, в этом массиве вариантов поля должны быть перечислены в том K>>>>> порядке, в каком к ним обращается парсер (чаще всего это порядок K>>>>> следования полей в выражении ключа, но необязательно). E>>>> С пустой датой получилось, спасибо: установил верхнюю и нижнюю границы E>>>> в ' '. E>>>> А если я хочу отобрать по двум полям из индекса (например, ключ E>>>> такой - 'dtos(DATE_G) + ID_EMIT', первое поле - дата, второе - E>>>> символьный идентификатор) с диапазоном дат 27.08.2003-28.08.2003 и E>>>> постоянным (или с диапазоном) идентификатором: E>>>> NTX3.Indexes[5].SetRangeFields('DATE_G;ID_EMIT',[StrToDate('27.08.2003'),'00000',StrToDate('28.08.2003'),'00000']) E>>>> В результате получается отбор только по первой дате. E>>>> Посмотрел парсер - там, что верхнему, что нижнему ключу, присваиваетс E>>>> одно значение '2003082700000', что и соответствует двум первым значениям E>>>> массива. E>>>> ??? K>>> Естественно. K>>> Ты смотрел реализацию NTX3.Indexes[5].SetRangeFields в исходниках? K>>> Вот она: K>>> Key := TrimRight(FKeyParser.EvaluteKey(FieldList, FieldValues)); K>>> NTXRange.HiKey := Key; K>>> NTXRange.LoKey := Key; K>>> NTXRange.ReOpen; K>>> И верхней и нижней границе присваивается одно значение ключа, K>>> посчитанное по значениям StrToDate('27.08.2003'),'00000'. K>>> И это не случайно, а для совместимости с SetRange в K>>> VCL. K>>> Тебе надо делать так: K>>> NTXRange.HiKey := '2003082700000'; K>>> NTXRange.LoKey := '2003082800000'; K>>> NTXRange.ReOpen; K>>> С пустой датой так: K>>> NTXRange.HiKey := ' 00000'; K>>> NTXRange.LoKey := '2003082800000'; K>>> NTXRange.ReOpen; E>> Так то оно так, но не катит. E>> Пробовал я уже. E>> Для пустых дат сделал так: E>> (Возможно, здесь где напортачил?) E>> NTXRange := TVKNTXRange.Create; E>> NTXRange.NTX := NTX3.Orders[5]; E>> //устанавливаем верхнюю и нижнюю границы E>> NTXRange.HiKey := ' '; E>> NTXRange.LoKey := ' '; E>> NTXRange.Active := true; E>> и для дипазона дат как у Вас пробовал, E>> а отбираются !все! записи K> Какой еще NTXRange := TVKNTXRange.Create; !!!! K> Ты чо! K> with NTX3.Orders[5].NTXRange do begin K> HiKey := ' 00000'; K> LoKey := '2003082800000'; K> ReOpen; K> end; 8))) Чайник) Понял Спасибо... |
Влад, а теперь маленький вопрос. Меня очень волнует скорость доступа к данным. Если можно хотя бы в двух словах, твои рекомендации. Я так понимаю, этот компонент ты делаешь не просто так и он имеет какое-то практическое применение. Заранее Спасибо. Игорь Самусь |
1) 1. Если есть индексы, обязательно сделай Flock - это кэширует индексные страницы, но имей в виду, что они все будут копиться в памяти, до тех пор, пока не сделаешь Unlock. (Для сброса индексных страниц на диск можно использовать for i := 0 to Indexes.Count - 1 do Indexes[i].Flush; после изменения, скажем каждой 1000 ключей). Или используй режим Exclusive. 2. Есть проперт property FastPostRecord: Boolean Она включает/выключает перечитку DataSet-а в операции Post, тебе надо выключить (FastPostRecord := true) 3. Лучше использовать следующую конструкцию (с точки зрени быстродействия): for i:=1 to Table1.RecordCount do begin Table1.SetTmpRecord(i); Table1.Edit; что-то делаем Table1.Post; end; Table1.CloseTmpRecord; 2) CashedUpdates нету. Есть только буферизация добавленных записей. procedure BeginAddBuffered(RecInBuffer: Integer); procedure FlushAddBuffer; procedure EndAddBuffered; Добавлю, еще пару слов. Indexes[i].Flush; сделать не получиться, она protected :) буферизация добавленных записей сильно увеличивает скорость массового добавления записей (в 4 - 6 раз). Поиграйся BufferSize. Если нет индексов операции чтения идут челиком в буфер. (Если есть, то только по записям.) Для быстрого прохождения по таблице есть метод DBEval с событием OnDBEval. Для быстрого прохождения по индексу есть метод SubIndex с событием OnSubIndex АД> Често говоря property FastPostRecord: Boolean не очень то и ускоряет. Да, это не очень сильное свойство, оно просто отключает перерисовку DataSet-а после Post, хотя слегка помогает. У тебя вообще, в принципе, какая версия работает? АД> // Я понимаю что я сделал что-то не-то но LookUp работает в любых вариантах АД> // Locate не проверял А какие такие варианты у lookup? Про FindKey: Я не нашел куска программы в FindKey АД> Было АД> // if ( item.page <> 0 ) then Pass(item.page); АД> // Result := false; там только в конце процедурины Pass: item := pNTX_ITEM(pChar(page) + page.ref[page.count]); if ( item.page <> 0 ) then Pass(item.page); Result := false; похоже вроде.... АД> Ты забыл про АД> BeginAddRecord; АД> EndAddRecord; АД> тоже ускоряет, Я не забыл, я не сказал, но это гораздо менее сильный ход чем BeginAddBuffered. АД> Но я не понял одного эта вещь не работает для визуальных компонентов или надо делать Refresh?. Ситуация такая: TDataSet сам по себе очень тормозная вешь. Поэтому любые примочки, которые не используют его виртуальные функции, а обращаются непосредственно к хранилищу данных по определению работают быстрее. К ним и относятс procedure BeginAddRecord; procedure EndAddRecord; procedure BeginAddBuffered(RecInBuffer: Integer); procedure FlushAddBuffer; procedure EndAddBuffered; это сильно ускаряет в 3 - 6 раз, если необходимо добавить сразу и много. procedure SetTmpRecord(nRec: DWORD); procedure CloseTmpRecord; Независимая от DataSet установка текущей записи, например: SetTmpRecord(5); Edit; Fields[0].AsInteger := ... Post; CloseTmpRecord; Будет работать гораздо быстрее чем Recno := 5; Edit; Fields[0].AsInteger := ... Post; Независимый от DataSet проход по таблице DBEval с возбуждением событи OnDBEval. Событию передается номер записи, но внутри события не надо делать SetTmpRecord/CloseTmpRecord. Это уже сделано автоматически внутри DBEval. Независимый от DataSet проход по индексу SubIndex с возбуждением событи OnSubIndex. Осуществляет проход по индексу в некоторых границах. В отличае от DBEval внутри события OnSubIndex необходимо делать SetTmpRecord/CloseTmpRecord, ну если Вас вообще интересует запись. но за удавольствие надо платить, все DB-Aware контролы не замечают того, что Вы делаете с DataSet-ом. Да, и еще, если нет индексов (или SetOrder(0)), операции чтения таблицы с диска проходят блоками, размером BlockSize АД> Indexes[i].Flush; сделать не получиться, она protected :) АД> Так можно делать или нет? Нет, нельзя, оно protected. Оно делается автоматом из UnLock. С индексами следующая тонкость: Всегда включина буферизация индексных страниц. Компонент загружает в память все страницы, к которым было когда либо хоть одно обращение ( это вскорости буду переделывать на ограниченное число страниц в памяти). В заголовке индекса храниться номер версии индекса. При любом обращении к индексу всегда сравниваются версии хранящегося в памяти индекса и текущего из только что считанного заголовка. Если они совпадают, то ничего не происходит, страницы продолжают копиться в памяти, если не совпадают, то компонент "забыват" о накопленном буфере и начинает копить его заново. Теперь: компонент менят таблицу и вместе с ней меняется индекс. Сдесь 2 варианта: RLock и FLock, с соответсвующим UnLock. Ну дык вот, на UnLock компонент сбрасывает все измененные страницы на диск (Flush) и увеличивает на 1 номер версии индекса и тоже записывает его на диск вместе с корневой страницей. А вывод очевиден: Делать FLock, вместо RLock, где это только возможно. Unlock после модификаций. И в промежутке между FLock/Unlock делать как можно больше этих модификаций. Edit автоматом делает Rlock Post снимает RLock (но не Flock!!), если Rlock - единственна блокировка, то делает Indexes.Flush Unlock снимает все блокировки (и FLock тоже) и делает Indexes.Flush всегда. Rlock и FLock реентерабельны, их можно вызывать хоть милион раз одно в другом на одни и те-же записи, если они уже заблокированны ничего не произойдет (в одном экземпляре компонента). Ну, и последнее есть массив заблокированных записей: property LockRecords: TList read FLockRecords; |
LOCKING SCHEMA IN VKDBF |
Clipper share mode AccessMode = 66 Clipper exclusive mode AccessMode = 18 All locks realised by call OS (Windows, DOS, ...) functions FileLock(handler, offset, NumBytes) and FileUnLock(handler, offset, NumBytes) There are 3 lock functions in VKDBF protected LockHeader() lock 1 byte for offset 1000000000 public RLock(nRec) lock 1 byte for offset 1000000000 + nRec public Flock() lock 1000000000 bytes for offset 1000000000 And 3 unlock protected UnlockHeader() unlock 1 byte for offset 1000000000 public RUnLock(nRec) unlock 1 byte for offset 1000000000 + nRec public UnLock() unlock 1000000000 bytes for offset 1000000000 This VKDBF lock and unlock functions invoke from the next VKDBF functions and procedures: PROTECTED protected InternalPost (invoke from public VKDBF.Post()) RLock(nRec) physical writing on disk RUnlock(nRec) protected InternalAddRecord (invoke from public VKDBF.Post() !!!!!!!! not from DataSet.Append or DataSet.Insert; public DataSet.AddRecord; public VKDBF.AddRecord(...); public VKDBF.EndAddRecord) LockHeader() RLock(LastRec + 1) physical writing on disk RUnlock(LastRec + 1) UnLockHeader() protected DeleteRecallRecord (invoke from public DataSet.Delete; public VKDBF.DeleteRecord; public VKDBF.RecallRecord; in DBF record only marked as "DELETED", the physical remove deleted records happens when Pack() method call) RLock(nRec) physical writing on disk RUnlock(nRec) protected InternalLast (invoke from public DataSet.Last;) LockHeader() physical read from disk UnLockHeader() protected GetRecordCount (invoke from public DataSet.recordCount property) LockHeader() physical read from disk UnLockHeader() PUBLIC public FlushAddBuffer; LockHeader() physical writing on disk the block of records UnLockHeader() public SetAutoInc, GetCurrentAutoInc, GetNextAutoInc LockHeader() physical read/write from/on disk UnLockHeader() public Pack LockHeader() physical write on disk UnLockHeader() public Truncate LockHeader() physical write on disk UnLockHeader() public ReindexAll LockHeader() physical read from disk UnLockHeader() public DBEval LockHeader() loop by your scope RLock(nRec) fire event OnDBEval RUnlock(nRec) UnLockHeader() Any read/write operation for index file do with LockHeader() (for index file) read/write operation in index file UnLockHeader() (for index file) WaitBusyRes: when VKDBF determinate that resource is Busy (record, file, header) VKDBF are waiting WaitBusyRes time (in milisec !!!!!) and raise exception. All aforesaid means: you add record(s) to the table: vkdbf.append; fkdbf.field1.value := .. ... vkdbf.post; //all locks and physical write do here !!! //see InternalAddRecord or you can add many recors vkdbf.BeginAddBuffered(100) i := 0 loop i 0..250 vkdbf.append; fkdbf.field1.value := .. ... vkdbf.post; //The recors accumulate in inner buffer. //When i = 100 invike FlushAddBuffer //(see FlushAddBuffer lock scema) end loop vkdbf.EndAddBuffered; //invike FlushAddBuffer for rest 50 recors vkdbf.edit; fkdbf.field1.value := .. ... vkdbf.post; //all locks and physical write do here !!! //see InternalPost lock scema in addition you can lock record explicitly: if vkdbf.RLock then begin vkdbf.edit; fkdbf.field1.value := .. ... vkdbf.post; //Then RUnLock in post method release the record end; you can lock file explicitly: if FLock() then begin ... UnLock() //ATTANTION release all lock records end; And finally there is a property LockRecords: TList read FLockRecords; this is a list all locked records by current instance of VKDBF. |
|
|
-------------------------------------- ICQ History Log For: 98903427 Pavel Started on Tue Aug 12 11:17:44 2003 -------------------------------------- Pavel 12.08.20 10:12 Здравствуте Влад! Не сильно заняты? VKar 12.08.20 10:12 Здравствуй Мы знакомы? Pavel 12.08.20 10:14 Да я одно время у вас просил разрешения на публикацию вашей статьи по Интергации MS Office с CAVO VKar 12.08.20 10:14 Я давно не пишу на CAVO Pavel 12.08.20 10:15 :) да я не пока КАВО Pavel 12.08.20 10:15 я по VKReportу хотел задать вопрос Pavel 12.08.20 10:15 если конечно можно VKar 12.08.20 10:15 Задавай Pavel 12.08.20 10:16 Можно ли узнать в VKReporte для Excel таблицы с какой строки начинается секция и на какой заканчиваетс Pavel 12.08.20 10:17 мне например нужно сделать итоговую сумму по столбцам. VKar 12.08.20 10:19 Можно. Для Excel-я надо использовать событие OnDataRequest, там в параметрах есть абсолютные и относительные координаты. Но сумму надо считать не через формулу Excel (формулу в ячейку ты не вделыешь, только через строку с последующим пересчетом) Pavel 12.08.20 10:22 т.е. будет только последний конечный результат VKar 12.08.20 10:22 Суммы считаются вручную в событиях OnSectionComplet (или OnSectionEnd) на BODY, вручную обнуляются, если это суммы по группам и подгруппам, и вручную же выводятся в эти самые группы, подгруппы и футоры как обычно по меткам VKar 12.08.20 10:26 в Excele метки лучше делать числовые, но форматы ставить какие надо для ячееек. типа нужна дата, ставишь в ячейку 1(2, 3, ...) а формат делаешь даты В OnDataRequest тебе приходит 1(2, 3, ...) ты в ответе в вариант пихаешь Excel дату (обычный флоат- количество дней с какого-то 70 года (посмотри сам конкретно), дробная часть - часть суток) получаеш то что надо VKar 12.08.20 10:27 Это же ты на моей странице спрашивал как в Excel воткнуть что-то кроме строки Pavel 12.08.20 10:28 в форуме? VKar 12.08.20 10:28 да Pavel 12.08.20 10:28 последнаяя тема мо Pavel 12.08.20 10:29 жалко что нельзя вставить формулу Excel :( VKar 12.08.20 10:30 Там сложный парсер надо писать для формул и нет горантии, что мелкософт не изменит логику его работы в будующем Pavel 12.08.20 10:32 это понятно, ну нельзя так нельз Pavel 12.08.20 10:36 а если в ячейку вставить строку,что-то типа такого '=СУММА(E6:E100)', сработает или нет? Pavel 12.08.20 10:37 еще один вопрос, а обязательно использовать контейнер и вообще для чего он нужен? VKar 12.08.20 10:39 Сработает, но в конце на OnEndReport надо сделать нечто, чтобы втолковать Excel-ю что это не строка а формула, я не помню чего конкретно, типа пересчитать, али чего-то еще, у меня студент занимался этим, но я конкретно не знаю что он делал Pavel 12.08.20 10:40 тогда попробую VKar 12.08.20 10:46 Контейнер использовать не обязательно Нужен он если одни и теже отчеты нужно делать в разные форматы, подпиховаешь ему разные бланки (разные обекты отчетов) а он их по одному коду заполняет Но там свои тонкости, типа в RTF/HTML/TXT отчете нет события OnDataRequest (точнее есть-то оно есть, но никогда не вызывается), поэтому если хочешь чтоб один код работал и на RTF и на Excel надо использовать только OnRequestByNumber(Name) Pavel 12.08.20 10:46 Спасибо что раъяснили, буду переваривать. А репорт хороший получился, работает быстрее чем то как писал я. Pavel 12.08.20 10:47 и еще появился вопрос :) VKar 12.08.20 10:48 Задавай Pavel 12.08.20 10:49 У вас в примере вы используете ReportContainer1.OnOldSectionComplit для чего он нужен? if Assigned(ReportContainer1.OnOldSectionComplit) then ReportContainer1.OnOldSectionComplit(self, SectionName, SectionNum) else if SectionName = 'BODY' then begin Inc(eee); Caption := IntToStr(eee); end; end; VKar 12.08.20 10:51 Это он в Caption выводин количество выведенных секций BODY Pavel 12.08.20 10:52 понятно |
|
|
|